package com.urbane.auth.utils;

import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT 工具类
 * 负责Token的生成、解析、校验
 * 使用JJWT库实现
 */
@Component // 声明为Spring组件，方便注入和管理
public class JwtUtil {

    /**
     * JWT 签名密钥（Base64编码格式）
     * 从application.yml配置文件中注入，例如：jwt.secret=your-256-bit-secret-your-256-bit-secret-
     * 注意：生产环境务必使用复杂且保密的密钥，并通过安全的方式（如环境变量、配置中心）管理，切勿硬编码在代码中！
     */
    @Value("${jwt.secret}")
    private static String secret;

    /**
     * JWT 过期时间（毫秒）
     * 从配置文件中注入，例如：jwt.expiration=86400000 (24小时)
     */
    @Value("${jwt.expiration}")
    private static long expiration;

    /**
     * 将Base64编码的字符串密钥转换为HMAC-SHA算法所需的SecretKey对象
     *
     * @return 用于签名和验签的密钥
     */
    private static SecretKey getSigningKey() {
        byte[] keyBytes = Decoders.BASE64.decode(secret);
        return Keys.hmacShaKeyFor(keyBytes);
    }

    /**
     * 生成JWT Token
     *
     * @param userId   用户唯一标识
     * @param username 用户名
     * @param roles    用户角色列表（多个角色用逗号分隔，例如："USER,ADMIN"）
     * @return 生成的JWT字符串
     * <p> <a href="https://github.com/jwtk/jjwt">jjwt 工具官网</a>
     * <a href="https://github.com/jwtk/jjwt?tab=readme-ov-file#creating-a-jwt/">jwt.io</a>
     * <p> <a href="https://www.jwt.io/">jwt.io</a>
     */
    public static String generateToken(String userId, String username, String roles) {
        // 1. 准备JWT的负载（Payload）中的自定义声明（Claims）
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId); // 存放用户ID
        claims.put("username", username); // 存放用户名
        claims.put("roles", roles); // 存放用户角色

        // 2. 计算Token的过期时间
        Date expiryDate = new Date(System.currentTimeMillis() + expiration);

        // 3. 使用JJWT构建器创建Token
        return Jwts.builder()
                .header()
                .keyId("aKeyId")
                .and()
                // 添加自定义声明
                .issuer("lcqh2635")
                // 设置签发时间
                .issuedAt(new Date())
                // 设置主题（通常放用户ID）
                .subject("Bob")
                // 设置过期时间
                .expiration(expiryDate)
                // 添加自定义声明
                .claims(claims)
                // 使用密钥和算法进行签名
                .signWith(getSigningKey())
                // 生成最终的JWT字符串
                .compact();
    }

    /**
     * 从JWT Token中解析出负载（Claims）信息
     *
     * @param token JWT字符串
     * @return 包含所有声明信息的Claims对象
     * @throws ExpiredJwtException      Token已过期
     * @throws MalformedJwtException    Token格式错误
     * @throws SecurityException        签名验证失败（无效Token）
     * @throws IllegalArgumentException 传入的Token为空或非法
     *                                  <p> <a href="https://github.com/jwtk/jjwt">jjwt 工具官网</a>
     *                                  <p> <a href="https://github.com/jwtk/jjwt?tab=readme-ov-file#constant-parsing-key/">jwt.io</a>
     *                                  <p> <a href="https://www.jwt.io/">jwt.io</a>
     */
    public static Claims parseToken(String token) {
        // 使用相同的密钥解析Token，如果解析成功则说明签名有效
        return Jwts.parser()
                .verifyWith(getSigningKey())
                .build()
                .parseSignedClaims(token) // 设置验签的密钥
                .getPayload(); // 获取负载（Payload）部分，即Claims
    }

    /**
     * 校验JWT Token是否有效
     * 1. 解析是否成功（签名是否正确）
     * 2. 是否过期（Expiration）
     *
     * @param token 待校验的Token
     * @return true-有效，false-无效
     * <p> <a href="https://github.com/jwtk/jjwt">jjwt 工具官网</a>
     * <p> <a href="https://www.jwt.io/">jwt.io</a>
     */
    public static boolean validateToken(String token) {
        try {
            // 尝试解析Token，如果过程中没有抛出异常，则认为Token有效
            parseToken(token);
            return true;
        } catch (ExpiredJwtException e) {
            // Token已过期
            // 这里可以记录日志，用于监控过期Token的访问频率
            // log.warn("JWT token has expired: {}", e.getMessage());
        } catch (MalformedJwtException e) {
            // Token格式错误（例如，不是三段式）
            // log.warn("Invalid JWT token: {}", e.getMessage());
        } catch (SecurityException | IllegalArgumentException e) {
            // 签名无效 或 Token为空/非法
            // log.warn("JWT signature validation failed or token is empty: {}", e.getMessage());
        } catch (Exception e) {
            // 捕获其他所有未知异常
            // log.error("Unexpected error during JWT validation: {}", e.getMessage());
        }
        return false;
    }

    /**
     * 专门判断Token是否已过期
     * 注意：此方法不验证签名，只检查过期时间！
     * 适用于需要明确知道过期状态而不是简单有效/无效的场景
     *
     * @param token JWT字符串
     * @return true-已过期，false-未过期或无法判断
     */
    public static boolean isTokenExpired(String token) {
        try {
            // 直接获取过期时间并与当前时间比较
            Claims claims = parseToken(token);
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (ExpiredJwtException e) {
            // 如果捕获到ExpiredJwtException，说明Token已过期
            // 我们可以从这里直接获取Claims，避免重复解析
            return true;
        } catch (Exception e) {
            // 对于其他任何异常（签名无效、格式错误等），我们无法判断过期状态
            // 可以记录日志，但返回true或false取决于业务逻辑
            // 通常认为无效的Token等同于"已过期"或"不可用"
            // log.warn("Failed to check token expiration, token may be invalid: {}", e.getMessage());
            return true; // 保守策略：视为已过期
        }
    }

    /**
     * 从Token中获取用户ID（从subject字段）
     *
     * @param token JWT字符串
     * @return 用户ID
     */
    public String getUserIdFromToken(String token) {
        Claims claims = parseToken(token);
        return claims.getSubject(); // JWT标准规定Subject通常存放主体标识（用户ID）
    }

    /**
     * 从Token中获取用户名（从自定义声明中）
     *
     * @param token JWT字符串
     * @return 用户名
     */
    public String getUsernameFromToken(String token) {
        Claims claims = parseToken(token);
        return claims.get("username", String.class); // 从自定义声明中获取
    }

    /**
     * 从Token中获取用户角色（从自定义声明中）
     *
     * @param token JWT字符串
     * @return 用户角色字符串
     */
    public String getRolesFromToken(String token) {
        Claims claims = parseToken(token);
        return claims.get("roles", String.class); // 从自定义声明中获取
    }

    /**
     * 获取Token的过期时间
     *
     * @param token JWT字符串
     * @return 过期时间Date对象
     */
    public Date getExpirationDateFromToken(String token) {
        Claims claims = parseToken(token);
        return claims.getExpiration();
    }

    /**
     * 检查Token是否即将过期（例如在指定毫秒数内过期）
     * 可用于实现Token的自动刷新逻辑
     *
     * @param token           JWT字符串
     * @param refreshInterval 刷新间隔（毫秒），例如300000（5分钟）
     * @return true-即将过期，需要刷新；false-不需要刷新
     */
    public boolean isTokenExpiringSoon(String token, long refreshInterval) {
        Date expiration = getExpirationDateFromToken(token);
        // 计算当前时间加上刷新间隔后的时间
        Date refreshTime = new Date(System.currentTimeMillis() + refreshInterval);
        // 如果过期时间早于这个“刷新时间点”，则认为即将过期
        return expiration.before(refreshTime);
    }
}